﻿using System;
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;


public class LedgeGrabbing : MonoBehaviour
{

    [Header("References")]
    public PlayerMovement playerMovement;
    public Transform orentation;
    public Transform cam;
    private Rigidbody rb;
    public Vector2 moveInput;

    [Header("Ledge Detection")]
    public float ledgeDetectionLength;
    public float ledgeSphereCastRadius;
    public LayerMask whatIsLedge;

    [Header("Ledge Grabbing")]
    public float moveToLedgeSpeed;
    public float maxLedgeGrabDistance;

    public float minTimeOnLedge;
    private float timeOnLedge;

    public bool holding;

    private Transform lastLedge;
    private Transform currLedge;

    private RaycastHit ledgeHit;
    [Header("Ledge Jump")]
    public bool jumpRequest;
    public float ledgeJumpForce;
    public float ledgeJumpUpwardForce;
    [Header("Exiting")]
    public bool exitingLedge;
    public float exitLedgeTime;
    private float exitLedgeTimer;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        playerMovement = GetComponent<PlayerMovement>();
        playerMovement.OnJumpAction += OnJump;
    }

    private void OnJump(bool obj)
    {
        jumpRequest = obj;
    }

    private void Update()
    {
        LedgeDetection();
        SubStatemachine(); 
    }
    private void LedgeDetection()
    {
        bool ledgeDetected = Physics.SphereCast(transform.position, ledgeSphereCastRadius, cam.forward, out ledgeHit, ledgeDetectionLength, whatIsLedge);

        if (!ledgeDetected) return;
        float distanceToLedge = Vector3.Distance(transform.position, ledgeHit.transform.position);
        if (ledgeHit.transform == lastLedge) return;

        if(distanceToLedge< maxLedgeGrabDistance)
        {
            EnterLedgeHold();
        }
    }
    private void SubStatemachine()
    {
        moveInput = playerMovement.inputDirection;
        if (holding)
        {
            FreezeRigibodyOnLedge();
            timeOnLedge += Time.deltaTime;
            if(timeOnLedge > minTimeOnLedge && moveInput.magnitude > 0.1f)
            {
                ExitLedgeHold();
            }

            if (jumpRequest)
            {
                LedgeJump();
            }
        }else if (exitingLedge)
        {
            if (exitLedgeTimer > 0)
            {
                exitLedgeTimer -= Time.deltaTime;
            }
            else
            {
                exitingLedge = false;
            }
        }
    }
    private void EnterLedgeHold()
    {
        holding = true;

        playerMovement.unlimited = true;
        playerMovement.restricted = true;

        currLedge = ledgeHit.transform;
        lastLedge = ledgeHit.transform;
        rb.useGravity = false;
        rb.velocity = Vector3.zero;
    }
    private void FreezeRigibodyOnEnter()
    {

    }
    private void FreezeRigibodyOnLedge()
    {
        rb.useGravity = false;
        Vector3 directionToLedge = (currLedge.position - transform.position).normalized;
        float distanceToLedge = Vector3.Distance(transform.position, currLedge.position);
        if(distanceToLedge > 1f)
        {
            if(rb.velocity.magnitude < moveToLedgeSpeed)
            {
                rb.AddForce(directionToLedge * moveToLedgeSpeed*1000f * Time.deltaTime);
            }
        }
        else
        {
            if (!playerMovement.freeze)
                playerMovement.freeze = true;
            if(playerMovement.unlimited) playerMovement.unlimited = false;
        }

        if(distanceToLedge>maxLedgeGrabDistance) ExitLedgeHold();
    }
    private void ExitLedgeHold()
    {

        exitingLedge = true;
        exitLedgeTimer = exitLedgeTime;

        holding = false;
        timeOnLedge = 0;
        playerMovement.freeze = false;
        playerMovement.restricted = false;
        rb.useGravity = true;
        StopAllCoroutines();
        Invoke(nameof(ResetLastLedge), 1f);
    }

    private void ResetLastLedge()
    {
        lastLedge = null;
    }
    void LedgeJump()
    {
        ExitLedgeHold();
        Invoke(nameof(DelayJumpForce), 0.05f);
    }

    void DelayJumpForce()
    {
        Vector3 forceToAdd = cam.forward * ledgeJumpForce + orentation.up * ledgeJumpUpwardForce;
        rb.velocity = Vector3.zero;
        rb.AddForce(forceToAdd, ForceMode.Impulse);
    }
}
